/*
* Copyright 2011 Pavel Kozhin 
*
* Licensed under the Apache License, Version 2.0 (the "License"); 
* you may not use this file except in compliance with the License. 
* You may obtain a copy of the License at 
* 
* http://www.apache.org/licenses/LICENSE-2.0
* 
* Unless required by applicable law or agreed to in writing, software 
* distributed  under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 
* See the License for the specific language governing permissions and 
* limitations under the License.
*
* Author: Pavel Kozhin.
*/
package org.etherframework.dependency
{
	import flash.errors.IllegalOperationError;
	import flash.utils.Dictionary;
	
	import org.etherframework.core.$Globals;
	import org.etherframework.core.bridge.IBridge;
	import org.etherframework.core.bridge.IBridgeFacades;
	import org.etherframework.core.context.Context;
	import org.etherframework.core.context.IContexts;
	import org.etherframework.core.context.IContextual;
	import org.etherframework.core.context.ISingleContextual;
	import org.etherframework.core.module.IModulePartFacade;
	import org.etherframework.core.module.ModulePart;
	import org.etherframework.core.module.ModulePartFactory;
	import org.etherframework.core.utils.ClassUtils;
	
	[ExcludeClass]
	public class Dependencies extends ModulePart implements IDependencies, ISingleContextual
	{
		private var _contexts:IContexts;
		private var _dictionary:Dictionary = new Dictionary();
		private var _owner:IDependencies;
		private var _context:Context;
		
		public static function initialize():void
		{
			$Globals.config.dependenciesFactory = new ModulePartFactory(Dependencies);
		}
		
		public function Dependencies(owner:IDependencies, context:Context)
		{
			if (!context)
				throw new ArgumentError("Argument context:Context can not be null.");
			
			_owner = owner;
			_context = context;
		}
		
		override public function init(bridge:IBridge):void
		{
			super.init(bridge);
			_contexts = bridge.contexts;
		}
		
		public function addClass(generator:Class, typeOf:Class = null):Dependency
		{
			if (!generator)
				throw new ArgumentError("Arguments generator:Class can not be null.");
			if (typeOf && generator != typeOf && !ClassUtils.extendsOrImplementsType(generator, typeOf))
				throw new ArgumentError("Class " + generator + " should extend or implement type " + typeOf);
				
			if (!typeOf)
				typeOf = generator;
			
			if (_dictionary[typeOf])
				throw new IllegalOperationError("Dependency " + typeOf + " is already added.");
			
			return (_dictionary[typeOf] = createDependency(typeOf, generator, true, false));
		}
		
		public function addSingleton(generator:Class, typeOf:Class = null):Dependency
		{
			if (!generator)
				throw new ArgumentError("Arguments generator:Class can not be null.");
			if (typeOf && generator != typeOf && !ClassUtils.extendsOrImplementsType(generator, typeOf))
				throw new ArgumentError("Class " + generator + " should extend or implement type " + typeOf);
			
			if (!typeOf)
				typeOf = generator;
			
			if (_dictionary[typeOf])
				throw new IllegalOperationError("Dependency " + typeOf + " is already added.");
			
			return (_dictionary[typeOf] = createDependency(typeOf, generator, true, true));
		}
		
		public function addInstance(instance:Object, typeOf:Class = null):Dependency
		{
			if (!instance)
				throw new ArgumentError("Argument instance:Object can not be null.");
			if (instance is Class)
				throw new ArgumentError("Argument instance:Object can not be a Class.");
			if (typeOf && !(instance is typeOf))
				throw new ArgumentError("Object " + instance + 
					" should extend or implement type " + typeOf);
			
			if (!typeOf)
				typeOf = instance["constructor"];
			
			if (_dictionary[typeOf])
				throw new IllegalOperationError("Dependency " + typeOf + " is already added.");
			
			return (_dictionary[typeOf] = createDependency(typeOf, instance, false, false));
		}
		
		public function getDependency(typeOf:Class):Dependency
		{
			return _dictionary[typeOf] || (_owner ? _owner.getDependency(typeOf) : null);
		}
		
		public function removeDependency(value:Dependency):void
		{
			if (!value)
				throw new ArgumentError("Argument value:Dependency can not be null.");
			if (!_dictionary[value.type])
				throw new IllegalOperationError("There is no dependency with type " + value + ".");
			
			delete _dictionary[value.type];
		}
		
		override public function destroy():void
		{
			_contexts.dispose(this);
		}
		
		public function contextual(context:Context):IDependencies
		{
			return _contexts.contextual(context, IDependencies, this) as IDependencies;
		}
		
		public function create(context:Context, type:Class):IContextual
		{
			var result:Dependencies = new Dependencies(this, context);
			result.init(bridge);
			return result;
		}
		
		public function dispose():void
		{
			_dictionary = null;
			_contexts = null;
			_owner = null;
		}
		
		public function get context():Context { return _context; }
		
		override public function getFacade(owner:IBridgeFacades):IModulePartFacade
		{
			return new DependenciesFacade(owner, this);
		}
		
		private function createDependency(key:Class, value:*, 
										  isGenerator:Boolean, 
										  isSingleton:Boolean):Dependency
		{
			return new Dependency(key, value, isGenerator, isSingleton, bridge, _context);
		}
	}
}